#include "stm32f1xx_hal.h"
#include "ota.h"
#include "flash_on_chip.h"

// 将函数放到 RAM 中执行，避免 Flash 在擦写时无法取指而死机
#if defined(__CC_ARM) || defined(__ARMCC_VERSION)
#define RAMFUNC __attribute__((section(".ramfunc")))
#elif defined(__GNUC__)
#define RAMFUNC __attribute__((section(".ramfunc"))) __attribute__((noinline))
#else
#define RAMFUNC
#endif

/**
 * @brief 等待 Flash 控制器空闲（BSY=0）
 * @param SR 指向 FLASH_SR 寄存器地址
 */
static inline void flash_wait_not_busy(volatile uint32_t *SR)
{
    while (*SR & 0x1)
    { /* 等待 BSY 清零 */
    }
}

/**
 * @brief  将暂存区固件拷贝覆盖到 APP 基址 (0x08000000)，并最终复位
 * @param  src   暂存区起始地址
 * @param  size  待拷贝字节数
 * @note   整个函数运行在 RAM 中（通过 RAMFUNC 标记）。
 *         过程：
 *         1) 解锁 FLASH
 *         2) 从 0x08000000 起按页擦除，覆盖 size 大小
 *         3) 从暂存区逐半字编程写入 APP 区
 *         4) 上锁并触发系统复位
 *
 */
RAMFUNC static void ram_swap_copy(uint32_t src, uint32_t size)
{
    // FLASH 寄存器地址（参考 RM0008）
    volatile uint32_t *KEYR = (uint32_t *)0x40022004; // FLASH_KEYR
    volatile uint32_t *SR   = (uint32_t *)0x4002200C; // FLASH_SR
    volatile uint32_t *CR   = (uint32_t *)0x40022010; // FLASH_CR
    volatile uint32_t *AR   = (uint32_t *)0x40022014; // FLASH_AR

    __disable_irq(); // 禁止中断，防止中断取指访问 Flash

    // 1. 解锁 Flash 写保护（KEYR 顺序写两个魔数）
    *KEYR = 0x45670123;
    *KEYR = 0xCDEF89AB;

    // 2. 擦除 APP 区 [0x08000000, 0x08000000+size)
    uint32_t dst    = FLASH_BASE_ADDR;
    uint32_t remain = size;
    while (remain)
    {
        *CR |= (1u << 1); // PER=1，页擦除使能
        *AR = dst;        // 设置要擦的页地址
        *CR |= (1u << 6); // STRT=1，启动擦除
        flash_wait_not_busy(SR);
        *CR &= ~(1u << 1); // 清 PER 位

        dst += PAGE_SIZE; // 下一页
        remain -= (remain >= PAGE_SIZE ? PAGE_SIZE : remain);
    }

    // 3. 从暂存区逐半字编程到 APP 区
    uint32_t s = src;
    uint32_t d = FLASH_BASE_ADDR;
    remain = size;
    while (remain)
    {
        uint16_t hw;
        if (remain >= 2)
        {
            // 正常拷贝两个字节
            hw = *(uint8_t *)s | (*((uint8_t *)s + 1) << 8);
            s += 2;
            remain -= 2;
        }
        else
        {
            // 末尾不足 2 字节，补高字节 0xFF（等同于擦除态）
            hw = *(uint8_t *)s | (0xFFu << 8);
            s += 1;
            remain = 0;
        }

        *CR |= (1u << 0);           // PG=1，进入编程模式
        *(volatile uint16_t *)d = hw; // 半字写入
        flash_wait_not_busy(SR);
        *CR &= ~(1u << 0);          // 清 PG 位
        d += 2;
    }

    // 4. 锁定 Flash，防止误写
    *CR |= (1u << 7); // LOCK=1

    // 5. 复位 MCU，启动后直接运行新 APP
    NVIC_SystemReset();
}

/**
 * @brief  OTA API：从暂存区拷贝固件到基址（APP 区）
 * @param  staging_addr 暂存区起始地址
 * @param  size         固件大小
 * @note   实际调用的就是 ram_swap_copy()，后者执行完成会复位 MCU。
 */
void OTA_SwapToBase_FromStaging(uint32_t staging_addr, uint32_t size)
{
    ram_swap_copy(staging_addr, size);
}
